package com.hqy.oauth.aspect;

import com.hqy.oauth.dto.GmallResult;
import com.hqy.oauth.dto.ResultCodeEnum;
import com.hqy.oauth.util.IpUtil;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

/**
 *
 *
 * 在您的场景中，不需要使用Lua脚本。理由如下：
 * 业务场景允许：
 * 1.登录失败计数不需要绝对精确
 * 2.轻微的时间窗口浮动可以接受
 * 性能考虑：
 * 1.简单命令执行更快
 * 2. 避免Lua脚本编译开销
 * 维护成本：
 * 1.简单代码更易理解和维护
 * 2.减少潜在的错误点
 * 系统健壮性：
 * 1.即使在高并发下，结果也是可接受的
 * 2.不会导致系统级故障
 */
@Aspect
@Component
public class RequestLimitAspect {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Around("@annotation(com.hqy.oauth.aspect.FailRequestLimit)")
    public Object failRequestAroundAdvice(ProceedingJoinPoint point) throws Throwable {
        // 1. 获取注解配置
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        FailRequestLimit limit = method.getAnnotation(FailRequestLimit.class);

        // 2. 获取请求信息
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
                .currentRequestAttributes()).getRequest();
        String ip = IpUtil.getIpAddress(request); // 使用您现有的工具类

        // 3. 构建Redis Key
        String lockKey = StringUtils.isNotBlank(limit.lockKey()) ? limit.lockKey() : "login_fail:" + ip;

        // 4. 检查是否达到限流阈值（执行前）
        String failCountStr = stringRedisTemplate.opsForValue().get(lockKey);
        if (StringUtils.isNotBlank(failCountStr)) {
                int failCount = Integer.parseInt(failCountStr);
                if (failCount >= limit.maxRetries()) {
                    return createErrorResponse(limit);
                }
        }


        //5. 执行登录方法
        //对比这里发生异常，程序怎么进行，我还是必须得了解清楚的 todo
        //这里到原方法执行异常，原方法会捕获异常，不会抛出异常，只会返回错误响应，及时程序运行到最后一行，程序崩溃了或者重启了
        Object result = point.proceed();

        //6. 对登录失败结果进行处理
        if (result instanceof GmallResult){
            GmallResult gmallResult = (GmallResult) result;
            if (gmallResult.getCode() == null || gmallResult.getCode() != ResultCodeEnum.SUCCESS.getCode()){
                //增加Redis中失败的次数
                // 这里是否使用lua脚本，我可以看看网关那边代码具体是怎么做的，使不使用lua脚本，还是看具体的场景的要求 todo
                Long increment = stringRedisTemplate.opsForValue().increment(lockKey, 1);
                stringRedisTemplate.expire(lockKey, limit.timeWindow(), limit.unit());

                return result;
            }
        }
        return result;
    }

    private GmallResult<?> createErrorResponse(FailRequestLimit limit) {
        // 创建错误响应
        GmallResult<Object> errorGmallResult = new GmallResult<>();
        errorGmallResult.setCode(Integer.parseInt(limit.errorCode()));
        errorGmallResult.setMessage(limit.errorMessage());
        return errorGmallResult;
    }

}
